package ru.tennisworld.user.service.users.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ru.tennisworld.user.entities.TennisUser;
import ru.tennisworld.user.entities.TennisUserProfile;
import ru.tennisworld.user.exceptions.TennisUserServiceException;
import ru.tennisworld.user.mappers.TennisUserMapper;
import ru.tennisworld.user.repositories.TennisUserRepository;
import ru.tennisworld.user.service.ValidationService;
import ru.tennisworld.user.service.users.TennisUserProfileService;
import ru.tennisworld.user.service.users.TennisUserService;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Service
public class TennisUserServiceImpl implements TennisUserService {

    @Lazy
    @Autowired
    private TennisUserProfileService tennisUserProfileService;

    @Lazy
    @Autowired
    private ValidationService validationService;

    @Autowired
    private TennisUserRepository tennisUserRepository;

    @Autowired
    private TennisUserMapper tennisUserMapper;

    @Value("${user-service.lose-coefficient}")
    private Long loseCoefficient;

    private static final Logger LOG = LoggerFactory.getLogger(TennisUserServiceImpl.class);

    @Override
    public TennisUser getTennisUser(String username) {
        return tennisUserRepository.findByLogin(username).orElseThrow(
                () -> new TennisUserServiceException("Пользователь не найден!")
        );
    }

    @Override
    public List<TennisUser> getUserReferenceList(List<String> usernameList) {
        LOG.info("operation=TennisUserServiceImpl.getUserReferenceList, action=start, usernameList={}", usernameList);
        List<TennisUser> result = new ArrayList<>();
        usernameList.forEach(item -> result.add(getTennisUser(item)));
        LOG.info("operation=TennisUserServiceImpl.getUserReferenceList, action=end");
        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public TennisUser createNewServiceUser(TennisUser tennisUser) {
        LOG.info("operation=TennisUserServiceImpl.createTennisUser, action=start, tennisUser={}", tennisUser);
        if (Objects.isNull(tennisUser.getTennisUserProfile())) {
            tennisUser.setTennisUserProfile(tennisUserProfileService.createBaseProfile());
        } else {
            tennisUser.getTennisUserProfile().setTennisUser(tennisUser);
        }
        validationService.validateTennisUser(tennisUser);
        tennisUserRepository.save(tennisUser);
        LOG.info("operation=TennisUserServiceImpl.createTennisUser, action=end");
        return tennisUser;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public TennisUser updateTennisUserMainInfo(TennisUser upd, TennisUser src) {
        LOG.info("operation=TennisUserServiceImpl.updateTennisUserMainInfo, action=start, src={}, upd={}", src, upd);
        tennisUserMapper.updateTennisUser(src, upd);
        validationService.validateTennisUser(upd);
        tennisUserRepository.save(upd);
        LOG.info("operation=TennisUserServiceImpl.updateTennisUserMainInfo, action=end, result={}", upd);
        return upd;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateUserRating(TennisUser winner, TennisUser loser) {
        LOG.info("operation=TennisUserServiceImpl.updateUserRating, action=start");
        Long winnerOldRating = winner.getTennisUserProfile().getRating();
        Long loserOldRating = loser.getTennisUserProfile().getRating();
        final float e = (float) (
                1 / (1 + Math.pow (10,
                        ((double) (loserOldRating - winnerOldRating) / 400)
                )));
        winner.getTennisUserProfile().setRating((long) (winnerOldRating + 10 * (1 - e)));
        loser.getTennisUserProfile().setRating((long) (loserOldRating + loseCoefficient * (0 - e)));
        tennisUserRepository.save(winner);
        tennisUserRepository.save(loser);
        LOG.info("operation=TennisUserServiceImpl.updateUserRating, action=end");
    }

    @Override
    public boolean checkUserDuplication(String username) {
        return tennisUserRepository.findByLogin(username).isPresent();
    }
}

